import json
import os
from collections import OrderedDict
from urllib.parse import unquote, quote
from rest_framework.response import Response
from django.http import JsonResponse
from apk.serializers import *
from rest_framework.decorators import api_view
from rest_framework import status
from apk_parse_model.settings import MEDIA_ROOT
from apk.apk_parse import *
from PIL import Image
from io import BytesIO
from django.core.files.base import ContentFile
from apk.models_predict import *
'''    由于该项目量级较小，所以API接口书写选择使用函数式编程书写方式实现     '''

# ------------------------------AndroidManifest相关文件解析apk_androidManifest表------------------------------------

# 因为一些不知其意的导包问题，只能将对AndroidManifest的解析写在view文件中
import csv
import re
from androguard.misc import AnalyzeAPK
from pathlib import Path
import tempfile
current_dir = Path.cwd()


# --------------------------权限提取相关-----------------------------------------

# 将apk所解析出的权限转换为0，1数据集，构建输入数据集
def apkTopermission(permissions, package_name):
    Data = []
    PERMISSION_Labels = []
    permissions_label_path = str(current_dir) + "/train_allpermissions2（131）.csv"
    with open(permissions_label_path, mode='r', encoding='gbk') as file:
        data = csv.reader(file)
        first_data = next(data)
        PERMISSION_Labels = first_data[1:]
    for pers in PERMISSION_Labels:
        if pers in permissions:
            Data.append(1)
        else:
            Data.append(0)
    file_path = str(current_dir).replace("\\", "/") + "/upload_files/input_permissions/" + package_name + ".csv"
    with open(file_path, mode='w', encoding='gbk') as file:
        writer = csv.writer(file)
        writer.writerow(Data)
    return file_path

def get_basicInfo(a):
    # a对象：它是APK文件的APK对象，包含了APK的基本信息，例如包名、版本号等。

    # 主活动
    activities = a.get_main_activities()
    # a.get_activities()：返回一个包含APK文件中声明的所有Activity的列表。

    package_name = a.get_package()
    # a.get_package()：返回APK文件的包名。
    # 包名是Android应用的唯一标识符，用于确保应用在设备上的唯一性。

    permissions = []
    pattern = r'\.permission\.([^ \n\r\t\f\v]*)'
    for permission in a.get_permissions():
    # 这里要使用正则生成器获取权限字符串格式
        _ = re.search(pattern, permission)
        if(not _):
            continue
        permissions.append(_.group(1))
    # 将权限提取为模型输入数据格式
    permissions_path = apkTopermission(permissions, package_name)

    # a.get_app_name()：返回APK文件的应用名称。这个方法可以获取应用程序的用户可见名称，通常用于显示在设备屏幕上。
    app_name = a.get_app_name()

    # 权限提取转换为文件
    detail_permission = a.get_details_permissions()
    str_permissions = "{"
    i = 1
    for k, y in detail_permission.items():
        if i != 1:
            str_permissions += ", " + '"' + str(k) + '": "' + str(y) + '"'
        else:
            str_permissions += '"' + str(k) + '": "' + str(y) + '"'
        i += 1
    str_permissions += "}"
    text_io_wrapper = io.StringIO(str_permissions)
    # 读取_io.TextIOWrapper对象的内容
    file_content = text_io_wrapper.getvalue().encode('utf-8')  # 转换为字节串，因为FileField期望的是字节类型的数据
    # 创建一个ContentFile对象
    content_file = ContentFile(file_content, name=app_name + '.txt')  # app应用程序名作为文件的名称被保存

    app_icon_path= a.get_app_icon()
    # a.get_app_icon()：返回APK文件的应用图标所在的路径。
    # 返回值'res/drawable-xxhdpi/icon.png'是一个路径

    androidversion_code = a.get_androidversion_code()
    # a.get_androidversion_code()：返回APK文件的Android版本代码。这个版本代码是一个整数值，用于标识应用程序的Android版本。

    androidversion_name = a.get_androidversion_name()
    # a.get_androidversion_name()：返回APK文件的Android版本名称。
    # 这个版本名称是一个字符串，用于标识应用程序的人类可读版本。

    # AXML二进制文件
    # 之后展示的时候需要进行解析
    android_manifest = a.get_android_manifest_axml().get_xml()
    # android_manifest_path = str(parent_dir) + "\\android_manifest_dir\\"
    # # 二进制文件存储——不需要在进行额外处理
    # with open(android_manifest_path + app_name +".txt", 'wb') as f:
    #     f.write(android_manifest)

    # a.get_android_manifest_axml().get_xml()：返回APK文件的AndroidManifest.xml的二进制XML（AXML）表示。
    # AXML是AndroidManifest.xml的二进制版本，它需要解析才能被人类读懂

    # 通过路径寻找apk文件的图标文件-apk_path
    # 现在没有这部分的图片，简单生成一个
    new_image = Image.new('RGB', (100, 100), "white")
    temp_handle = io.BytesIO()
    new_image.save(temp_handle, 'PNG')
    temp_handle = temp_handle.getvalue()

    # 将数据写入数据库
    AndroidManifest = apk_AndroidmanifestAPK.objects.create(permissions=content_file, activities=activities,
                                          source_code=ContentFile(android_manifest, name=app_name + ".txt"),
                                          package_name=package_name,
                                          app_name=app_name, app_icon_path=app_icon_path,
                                          app_icon=ContentFile(temp_handle, name=app_name + ".png"),
                                          android_version_code=androidversion_code,
                                          android_version_name=androidversion_name,
                                          input_permissions=permissions_path
                                          )
    # 返回权限，便于此次解析直接用字典展示
    return detail_permission, AndroidManifest


# ----------------------------------签名文件的解析apk_signature表-----------------------------------------------
import hashlib
from androguard.core.apk import APK

# 测试路径
apk_path = "C:\\file_data\\pycharm_project\\django\\apk_parse_model\\测试apk.apk"
# 计算md5
def get_md5_hash(file_path):
    """
    计算并返回给定文件的 MD5 哈希值。
    :param file_path: 要计算 MD5 哈希值的文件路径
    :return: 文件的 MD5 哈希值（十六进制字符串）
    """
    hash_md5 = hashlib.md5()
    with open(file_path, "rb") as f:
        for chunk in iter(lambda: f.read(4096), b""):
            hash_md5.update(chunk)
    return hash_md5.hexdigest()

# 获取签名有关的所有信息
def Get_signature(a2dex):
    a = a2dex
    # 是否签名以及v1\v2\v3是否签名
    is_signed = a.is_signed()
    is_signed_v1 = a.is_signed_v1()
    is_signed_v2 = a.is_signed_v2()
    is_signed_v3 = a.is_signed_v3()
    # 是否有效
    is_valid = a.is_valid_APK()
    # 签名名称
    signature_name = a.get_signature_name()
    cert = a.get_certificates()[0]
    # sha1和sha256指纹信息
    sha1 = cert.sha1_fingerprint
    sha256 = cert.sha256_fingerprint
    # 获取签名证书发布信息
    # md5
    md5 = get_md5_hash(apk_path)
    # 国家
    if 'country_name' in cert.issuer.native:
        country = cert.issuer.native['country_name']
    else:
        country = None
    # 省市
    if "state_or_province_name" in cert.issuer.native:
        province = cert.issuer.native['state_or_province_name']
    else:
        province = None
    # 城市
    if 'locality_name' in cert.issuer.native:
        city = cert.issuer.native['locality_name']
    else:
        city = None
    # 组织
    if 'organization_name' in cert.issuer.native:
        organization = cert.issuer.native['organization_name']
    else:
        organization = None
    # 服务器或服务名称
    if 'common_name' in cert.issuer.native:
        serviceName = cert.issuer.native['common_name']
    else:
        serviceName = None

    # 将数据写入数据库中
    Apk_signature = apk_signature.objects.create(is_signature=is_signed,
                                 is_valid=is_valid, signature_name=signature_name,
                                 v1=is_signed_v1, v2=is_signed_v2, v3=is_signed_v3,
                                 country=country, province=province, city=city,
                                 organization=organization, serviceName=serviceName,
                                 md5=md5, sha1=sha1, sha256=sha256)
    print("写入签名文件成功")
    return Apk_signature


# ------------------------------------相关诈骗站点related_url表------------------------------------------------
    # apk2url通过使用apktool和aapt两个工具对apk文件进行综合解析
    # 解析之后的url
    # _endpoints.txt //包含具有完整 URL 路径的终结点,更加的详细（选择这个）-写入文件，以文件的形式存储
    # 动态解析通联地址主要是域名
    # 文件在kali虚拟机中的路径
import paramiko
import time
# 通过该函数获取apk内部的相关站点
def parse_urls(apk_name):
    # -------------将pycharm与kali虚拟机连接-----------------------
    # 连接设置
    # 使用root用户登录kali虚拟机
    ssh_host = '192.168.204.128'  # 替Kali 虚拟机 IP 地址
    ssh_user = 'root'  # Kali 用户名
    ssh_password = '20050213w'  # Kali 密码

    # 创建 SSH 连接对象
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

    # 连接到 Kali 虚拟机
    try:
        ssh.connect(hostname=ssh_host, username=ssh_user, password=ssh_password)
        print('SSH connection established')
    except Exception as e:
        print(f"连接失败：{e}")
        exit(1)

    # ----------------------执行解析命令和读取解析后结果的命令----------------------------------------
    # 执行解析的命令
    command1 = "apk2url /mnt/hgfs/apk_file/" + apk_name
    print(command1)
    # exec_conmand每次开启的是一个新的shell，无法实现实时交互
    # invoke_shell执行多条连续的指令（同一个shell）
    # 创建一个shell会话
    shell = ssh.invoke_shell()

    # 执行多条命令
    # cd到特定的文件夹中，使用root解析会将解析内容解析在相应的子文件夹中
    parse_urls_path = "/home/wzd/parse_apk_url/"
    shell.send("cd " + parse_urls_path + "\n")
    time.sleep(1)
    shell.send(command1 + "\n")
    time.sleep(1)
    shell.send("n\n")
    # # 接收65535字节的数据输出-够用
    # recv_data = shell.recv(65535)
    # # 将接收到的字节数据解码为字符串，并添加到输出字符串中
    # output = recv_data.decode('utf-8')
    #
    # print(output)
    # 关闭连接
    shell.close()

    # 执行读取的命令
    _endpoints_path = parse_urls_path + 'endpoints/' + apk_name[:-4] + '_endpoints.txt'
    command2 = "cat " + _endpoints_path
    print(command2)
    res_url = []
    i = 1
    while(not res_url):
        time.sleep(1)
        print("等待中~已等待"+str(i)+"秒")
        i += 1
        stdin, stdout, stderr = ssh.exec_command(command2)
        for line in stdout.readlines():
            if not line.strip():
                continue
            res_url.append(line.strip())
    print(res_url)

    # 返回url内容-列表
    # 关闭ssh连接
    ssh.close()

    return res_url


def parse_DNS(apk_path):
    return "暂定"

def get_url(apk_path):
    apk_name = apk_path.split('/')[-1]
    # 获取url_list将其转换为文件存储，提供list便于展示
    res_url = parse_urls(apk_name)
    text_io_wrapper = io.StringIO(str(res_url))
    # 读取_io.TextIOWrapper对象的内容
    file_content = text_io_wrapper.getvalue().encode('utf-8')  # 转换为字节串，因为FileField期望的是字节类型的数据
    # 创建一个ContentFile对象
    url_content_file = ContentFile(file_content, name=apk_name[:-4] + '.txt')

    # 获取域名-数据结构为列表
    apk_DNS = parse_DNS(apk_path)
    text_io_wrapper = io.StringIO(str(apk_DNS))
    # 读取_io.TextIOWrapper对象的内容
    file_content = text_io_wrapper.getvalue().encode('utf-8')  # 转换为字节串，因为FileField期望的是字节类型的数据
    # 创建一个ContentFile对象
    DNS_content_file = ContentFile(file_content, name=apk_name + '.txt')
    # 写入数据库
    Apk_related_urls = apk_related_site.objects.create(apk_urls=url_content_file,
                                                       apk_DNS=DNS_content_file)
    return Apk_related_urls, res_url, apk_DNS


# --------------------------------------上传文件api接口以及所有表的存储实现-------------------------------

# 上传APK文件至本地
# post请求 上传完毕后会返回该apk经过解析之后的所有信息
# 这里需要思考如何实现外键的关联-可能权限那部分更加麻烦
def upload_file(request):
    if request.method == 'POST':
        if 'file' in request.FILES:
            file = request.FILES['file']
            # 将文件存储到数据库中
            if file.name.endswith('.apk'):
                # 获取文件信息
                # 这里保存的都是文件的路径
                file_name = file.name
                file_size = file.size
                # django会自动保存将FileField文件在项目主目录下面，可以通过设置MEDIA_ROOT规范位置
                # 实现上传文件的保存操作，如果是在测其他功能的话直接注释掉，不然会重复写入数据库以及本地
                Apk_files = apk_files.objects.create(data=file, file_name=file_name, file_size=file_size)
                print("完成文件的写入上传")
                # 这部分写对apk进行解析的程序，并且在解析过后直接将数据写入了数据库中
                # 根据保存位置直接通过名字就可以获取apk的路径
                apk_path = MEDIA_ROOT + "apk_file/" + file_name

                # ----------------------------功能函数实现分表加载----------------------
                # 提前将解析pak
                a2dex = APK(apk_path)
                a, d, dx = AnalyzeAPK(apk_path)
                # dex表解析
                Apk_dexInfo = extract_dex_from_apk(a2dex)
                print("完成对dex表的解析")

                # 签名表的解析
                Apk_signature = Get_signature(a2dex)
                print("完成对签名表的解析")

                # 主要是AndroidManifest表的解析
                detail_permission, Apk_androidManifest = get_basicInfo(a)
                print("完成对Android Manifest表的解析")

                # 对相关站点表的上传解析
                apk_url, res_url, res_dns = get_url(apk_path)
                print("完成对相关诈骗站点表的解析")

                print("完成所有分表的数据库加载")
                # --------------------------------总表加载------------------
                # 总表会自动对应分表的外键关联
                Apk_info = APK_info.objects.create(files=Apk_files,
                                      signature=Apk_signature,
                                      dexInfo=Apk_dexInfo,
                                      AndroidmanifestAPK=Apk_androidManifest,
                                      related_site=apk_url
                                      )
                print("完成总表的数据库加载")
                # ---------------------------------模型进行判别分析-------------------------
                # 首先需要先判断所上传的apk是否存在与库内的黑白名单库中
                # 如果不存在则进行解析，并将解析结果放入黑白名单库中

                # ---------------------------------序列化模型（拿到data数据）-----------------------------
                # data数据传送阶段：应该可以直接调用detail_apk获取
                print("序列化之前")
                data = APK_infoSerializer(Apk_info).data
                print("序列化之后")
                # 自定义data内容
                # 将权限添加进去（需要展示）
                # i = 1
                # permissions = {}
                # for permission in detail_permission:
                #     name = "permissions_" + str(i)
                #     Permission = {}
                #     Permission['permission_name'] = permission
                #     Permission['permission_type'] = detail_permission[permission][0]
                #     Permission['permission_detail'] = detail_permission[permission][1] + detail_permission[permission][2]
                #     permissions.update({name: Permission})
                #     i += 1
                # data.update({"permission": permissions})
                # 自定义数据写入 将所有id打包发送
                res_data = {}
                res_data.update({'AndroidManifest_id': data.get("AndroidmanifestAPK").get('id'),
                             "signature_id": data.get("signature").get('id'),
                             "dexInfo_id": data.get("dexInfo").get('id'),
                             "files_id": data.get("files").get('id'),
                             "related_site_id": data.get("related_site").get('id')})
                return JsonResponse(res_data, status=status.HTTP_201_CREATED, safe=False)
            return JsonResponse({'message': 'Invalid file type and not apk file'}, status=status.HTTP_400_BAD_REQUEST)
        return JsonResponse({'message': 'Invalid file type this is not a file'}, status=status.HTTP_400_BAD_REQUEST)


# 主要针对的是对于以前的已经识别过的apk文件的报告复现展示 需要指定对应的pk
# 主页面信息
def index_details(request):
    apks = APK_info.objects.all()
    print("序列化之前")
    last_data = []
    for apk in apks:
        res_data = {}
        serializer = APK_infoSerializer(apk)
        _data = serializer.data
        package_name = _data.get('AndroidmanifestAPK').get('package_name')
        file_name = _data.get('files').get('file_name')
        md5 = _data.get('signature').get('md5')
        create_date = _data.get('files').get('create_date')
        app_name = _data.get('AndroidmanifestAPK').get('app_name')
        res_data.update({'kind': "安卓", 'file_name': file_name, 'md5': md5, 'create_date': create_date, 'app_name': app_name})
        res_data.update({'package_name': package_name})
        last_data.append(res_data)
    return JsonResponse(last_data, status=status.HTTP_200_OK, safe=False)

# 返回java源代码
def get_java_source(request, pk):
    apk = APK_info.objects.get(pk=pk)
    serializers = APK_infoSerializer(apk)
    data = serializers.data
    return JsonResponse({'java_source': data, 'error': None})


# 返回apk_files做演示-主要是想明白怎么将一个文件展示到前端
def get_file(request, pk):
    try:
        obj = apk_files.objects.get(id=pk)
        serializer = APK_filesSerializer(obj)
        # 返回一个可以直接在浏览器中打开的文件，或者通过URL访问
        print(serializer.data)
        return JsonResponse(serializer.data)
    except apk_files.DoesNotExist:
        return JsonResponse({'error': 'File not found'}, status=status.HTTP_404_NOT_FOUND)

# 返回manifest源代码
def get_android_source(request, pk):
    data = []
    return JsonResponse({'android_source': None})

# 页面详细信息展示
def get_site(request, pk):
    related_site = apk_related_site.objects.get(pk=pk)
    # 序列化
    serializer = APK_related_sitesSerializer(related_site)
    # 存储url路径形式编码
    file_path = serializer.data.get('apk_urls')
    # 转换为中文编码
    file_path = unquote(file_path)
    # 拼接绝对路径
    file_path = str(current_dir) + '\\' + str(file_path)
    res_data = []
    with open(file_path, 'r') as f:
        dataset =f.readline()[1:-1].split(',')
        for i in dataset:
            res_data.append(i.strip()[1:-1])
    return JsonResponse(res_data, status=status.HTTP_200_OK, safe=False)

def get_permissions(request, pk):
    Android_manifest = AndroidmanifestAPKSerializer(apk_AndroidmanifestAPK.objects.get(pk=pk)).data
    permission_file = Android_manifest.get("permissions")
    # 将url编码转换为中文编码便于查找路径
    permission_file = unquote(permission_file)
    current_dir = Path.cwd()
    file_path = str(current_dir) + '\\' + permission_file
    # file_path = "C:\\file_data\\pycharm_project\\django\\apk_parse_model\\upload_files\\permissions\\黃品匯_HAzAEZW.txt"
    with open(file_path, 'r') as f:
        data = f.readline().strip()
        data = json.loads(data)
        data = dict(data)
        res_data = []
        for k, v in data.items():
            per = {}
            data = v[1:-1].split(',')
            detail = str(data[1][1:] + " and " + data[2][1:])
            per.update({'permission_name': k, 'permission_type': data[0][1:-1], 'permission_detail': detail})
            res_data.append(per)
    return JsonResponse(res_data, status=status.HTTP_200_OK, safe=False)

def get_signature(request, pk):
    signature = apk_signature.objects.get(pk=pk)
    serializer = SignatureSerializer(signature)
    data = serializer.data
    res_data = []
    res_data.append(data)
    return JsonResponse(res_data, status=status.HTTP_200_OK, safe=False)

def get_Android_manifest(request, pk):
    Android_manifest = apk_AndroidmanifestAPK.objects.get(pk=pk)
    serializer = AndroidmanifestAPKSerializer(Android_manifest)
    data = serializer.data
    data['activities'] = data.get('activities')[2:-2]
    res_data = []
    res_data.append(data)
    return JsonResponse(res_data, status=status.HTTP_200_OK, safe=False)

def get_fileInfo(request, pk):
    file = apk_files.objects.get(pk=pk)
    serializer = APK_filesSerializer(file)
    data = serializer.data
    res_data = []
    res_data.append(data)
    print(res_data)
    return JsonResponse(res_data, status=status.HTTP_200_OK, safe=False)

# 获取黑白名单库
def get_black_apk(request):
    _black_apk = black_apk.objects.all()
    res_data = []
    for b in _black_apk:
        res_data.append(black_apkSerializer(b).data)
    return JsonResponse(res_data, status=status.HTTP_200_OK, safe=False)

def get_white_apk(request):
    _white_apk = white_apk.objects.all()
    res_data = []
    for w in _white_apk:
        res_data.append(white_apkSerializer(w).data)
    return JsonResponse(res_data, status=status.HTTP_200_OK, safe=False)

# 获取指定文件的input_permissions或者dex_img_path模型输入数据
def get_data(request, pk):
    # 前端设置request数据 能够确定使用哪一个模型
    # 使用文件对应id查看对应的Android Manifest文件-在找到input_permissions的存储路径
    # 数据库不合理先注释掉
    apk_info = APK_info.objects.get(pk=pk)
    AndroidManifest_id = apk_info.AndroidmanifestAPK
    input_path = apk_AndroidmanifestAPK.objects.get(pk=66).input_permissions
    # 将权限数据准备好
    data = get_input_permissions(input_path)

    # 图像路径获取
    dex_info_id = apk_info.dexInfo
    img_path = apk_dexInfo.objects.get(pk=17).dex_image
    img_path = str(current_dir).replace("\\","/") + "/upload_files/" + str(img_path)
    print(img_path)

    return JsonResponse(data, status=status.HTTP_200_OK, safe=False)
# 解析url和二维码apk安装包
from apk.QR_code_anylor import *
# 接收上传的url连接下载apk网络文件
def post_online_apk(request):
    body_unicode = request.body.decode('utf-8')
    # 解析JSON数据为Python字典
    data = json.loads(body_unicode)
    apk_url = data.get('apk_url')
    print(apk_url)
    apk_save_path = str(current_dir).replace('\\', '/') + "/upload_files/post_apk/" + "/" + apk_url.split("/")[-1]
    print(apk_save_path)
    download_apk(apk_url, apk_save_path)
    return JsonResponse({'message': '上传成功'}, status=status.HTTP_200_OK)







